package com.idanchuang.component.webflux.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.idanchuang.component.core.Dc;
import com.idanchuang.component.core.ServiceInfo;
import com.idanchuang.component.core.helper.EnvContext;
import com.idanchuang.component.logback.helper.LogContext;
import com.idanchuang.component.shadow.core.ShadowContextMonitor;
import com.idanchuang.component.webflux.config.ComponentConfig;
import com.idanchuang.component.webflux.log.AccessLogger;
import com.idanchuang.component.webflux.util.IpUtils;
import com.zoi7.component.core.base.BaseClass;
import org.apache.skywalking.apm.toolkit.trace.TraceContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 统一web流量过滤
 */
public class StandardWebFilter extends BaseClass implements WebFilter {

    private static final Logger logger = LoggerFactory.getLogger(StandardWebFilter.class);

    private final ComponentConfig componentConfig;
    /**
     * 处理时间 线程绑定
     */
    private static final ThreadLocal<Long> DEAL_TIME_THREAD_LOCAL = new NamedThreadLocal<>("WebAccessDealTime");

    public StandardWebFilter(ComponentConfig componentConfig) {
        this.componentConfig = componentConfig;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        if (isAccessEnabled()) {
            DEAL_TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        }
        // collect request
        collectRequestHeader(exchange.getRequest());
        return chain.filter(exchange).then(Mono.fromRunnable(() -> doFilter(exchange)));
    }

    private void doFilter(ServerWebExchange exchange) {
        try {
            // log access
            doAccessLog(exchange);
        } catch (Exception e) {
            logger.error("", e);
        } finally {
            DEAL_TIME_THREAD_LOCAL.remove();
        }
    }

    /**
     * 打印访问日志
     * @param exchange 请求
     */
    private void doAccessLog(ServerWebExchange exchange) {
        if (!isAccessEnabled()) {
            return;
        }
        Long start = DEAL_TIME_THREAD_LOCAL.get();
        if (start ==  null) {
            return;
        }
        long rt = System.currentTimeMillis() - start;
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        // resolve url for pattern
        String url = resolveUrl(request.getPath().value());
        if (skipUrl(url, response)) {
            return;
        }

        Access access = new Access();
        access.setAppId(ServiceInfo.getAppId().toLowerCase());
        access.setGroup(ServiceInfo.getGroup());
        access.setVersion(ServiceInfo.getVersion());
        access.setTimestamp(start);
        access.setTime(formatTime(start));
        access.setProtocol("http");
        access.setMethod(request.getURI().getScheme());
        access.setRequestMethod(request.getMethodValue());
        access.setHost(request.getURI().getHost());
        access.setPort(request.getURI().getPort());
        access.setUrl(url);
        MultiValueMap<String, String> queries = request.getQueryParams();
        if (!queries.isEmpty()) {
            access.setQuery(queries.toString());
        }
        access.setServerAddr(ServiceInfo.address());
        access.setTraceId(EnvContext.getDcTraceId());
        String skyId = TraceContext.traceId();
        if (!"".equals(skyId)) {
            access.setSkyId(skyId);
        }
        access.setApmId(org.slf4j.MDC.get("apm-traceid"));
        access.setRequestTime(rt);
        access.setUserId(request.getHeaders().getFirst("userId"));
        HttpStatus httpStatus = response.getStatusCode();
        access.setStatus(httpStatus != null ? httpStatus.value() : -1);
        InetSocketAddress address = request.getRemoteAddress();
        access.setClientIp(address != null ? address.getAddress().getHostAddress() : "-");
        access.setRealClientIp(IpUtils.getClientIp(request));
        access.setRpcFrom(getRpcFromAppId(request).toLowerCase());
        access.setFromApi(getRpcFromApi(request));
        access.setFromGroup(getRpcFromGroup(request));
        access.setFromVersion(getRpcFromVersion(request));
        access.setReferer(request.getHeaders().getFirst("referer"));
        access.setUserAgent(request.getHeaders().getFirst("user-agent"));
        access.setOriginReq(EnvContext.getAttribute(Dc.ORIGIN_REQUEST));
        // 是否压测请求
        access.setPressure(EnvContext.isPressure() || ShadowContextMonitor.isShadowEnvironment() ? 1 : 0);
        // 打印header
        if (isLogHeaders()) {
            access.setHeaders(formatHeaders(request));
        }
        // 打印至access
        AccessLogger.info(access);
    }

    /**
     * 如果匹配url模板则使用模板, 未匹配模板则使用当前url
     * @param url 当前url
     * @return ..
     */
    private static String resolveUrl(String url) {
        String[] patterns = ComponentConfig.accessUrlPatternList;
        if (patterns == null || patterns.length == 0) {
            return url;
        }
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        for (String pattern : patterns) {
            if (pattern == null || pattern.isEmpty()) {
                continue;
            }
            if (antPathMatcher.match(pattern, url)) {
                return pattern;
            }
        }
        return url;
    }

    /**
     * 是否跳过此请求
     * @param url url
     * @param response 响应
     * @return ..
     */
    private boolean skipUrl(String url, ServerHttpResponse response) {
        if (componentConfig.isIgnoreLogHealth() && url.endsWith("/actuator/health")) {
            return true;
        }
        if (componentConfig.isIgnoreLogResource()) {
            String contentType = response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE);
            if (contentType == null) {
                return false;
            }
            return contentType.startsWith("application/javascript")
                    || contentType.startsWith("text/css")
                    || contentType.startsWith("image/")
                    || contentType.startsWith("video/")
                    || contentType.startsWith("text/html");
        }
        return false;
    }

    /**
     * 是否打印header
     * @return ..
     */
    private boolean isLogHeaders() {
        // 开启header打印
        if (componentConfig.isLogHeaders()) {
            return true;
        }
        // 是否为诊断请求
        return EnvContext.isDiagnoses();
    }

    private static String formatTime(long time) {
        Date date = new Date(time);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        return sdf.format(date);
    }

    private static String getRpcFromAppId(ServerHttpRequest request) {
        String rpcFrom = request.getHeaders().getFirst(Dc.RPC_FROM_NAME);
        if (rpcFrom == null) {
            rpcFrom = request.getQueryParams().getFirst(Dc.RPC_FROM_NAME);
        }
        return rpcFrom != null ? rpcFrom : Dc.DEFAULT_RPC_FROM;
    }

    private static String getRpcFromApi(ServerHttpRequest request) {
        String rpcFrom = request.getHeaders().getFirst(Dc.RPC_FROM_API_NAME);
        return rpcFrom != null ? rpcFrom : Dc.DEFAULT_RPC_FROM_API;
    }

    private static String getRpcFromGroup(ServerHttpRequest request) {
        String rpcFrom = request.getHeaders().getFirst(Dc.RPC_FROM_GROUP_NAME);
        return rpcFrom != null ? rpcFrom : Dc.DEFAULT_RPC_FROM_GROUP;
    }

    private static String getRpcFromVersion(ServerHttpRequest request) {
        String rpcFrom = request.getHeaders().getFirst(Dc.RPC_FROM_VERSION_NAME);
        return rpcFrom != null ? rpcFrom : Dc.DEFAULT_RPC_FROM_VERSION;
    }

    private boolean isAccessEnabled() {
        return this.componentConfig != null && this.componentConfig.isAccessLog();
    }

    private static String formatHeaders(ServerHttpRequest request) {
        Map<String, List<String>> map = new HashMap<>(request.getHeaders());
        return map.toString();
    }

    /**
     * 收集header信息, 并存入当前上下文
     * @param request ..
     */
    private void collectRequestHeader(ServerHttpRequest request) {
        try {
            // 清理当前残留的变量
            EnvContext.clearContext();

            // 将当前Path记录ThreadLocal
            EnvContext.setCurrentPath(request.getPath().toString());

            HttpHeaders httpHeaders = request.getHeaders();
            // 取出流量标, 保存流量标
            String headerValue = httpHeaders.getFirst(EnvContext.getEnvHeaderName());
            if (headerValue != null) {
                EnvContext.setCurrentEnv(headerValue);
            }
            // 取出前端traceId, 没有则生成一个
            String dcTraceId = httpHeaders.getFirst(EnvContext.getDcTraceIdHeaderName());
            if (dcTraceId == null) {
                dcTraceId = EnvContext.createDcTraceId();
            }
            EnvContext.setDcTraceIdIfAbsent(dcTraceId);
            // 取出时光机偏移量, 保存至上下文
            String tmo = httpHeaders.getFirst(Dc.TIME_MACHINE_OFFSET_HEADER);
            if (tmo != null && !tmo.isEmpty()) {
                EnvContext.setAttribute(Dc.TIME_MACHINE_OFFSET, tmo);
            }
            // 取出用户信息存入上下文
            String userId = httpHeaders.getFirst(Dc.USER_ID_HEADER);
            if (userId != null) {
                EnvContext.setAttribute(Dc.USER_ID, userId);
            }
            String userIdCode = httpHeaders.getFirst(Dc.USER_ID_CODE_HEADER);
            if (userIdCode != null) {
                EnvContext.setAttribute(Dc.USER_ID_CODE, userIdCode);
            }
            // 请求诊断
            String diag = httpHeaders.getFirst(Dc.DIAG_HEADER);
            if (notBlank(diag)) {
                EnvContext.setAttribute(Dc.DIAG, diag);
            }

            // 环境变量
            String attributes = httpHeaders.getFirst(EnvContext.TRANSMIT_ATTRIBUTE_HEADER_NAME);
            if (notBlank(attributes)) {
                attributes = URLDecoder.decode(attributes, "UTF-8");
                EnvContext.setAllAttribute(JSON.parseObject(attributes));
            }
        } catch (Exception e) {
            // 降级
            LogContext.setKeyword("componentError");
            logger.error("Collect Request Header error", e);
        }
    }

    private static class Access {

        /**
         * 服务
         */
        String appId;
        /**
         * 服务版本
         */
        String version;
        /**
         * 服务分组
         */
        String group;
        /**
         * 请求开始时间
         */
        String time;
        /**
         * 请求开始时间
         */
        long timestamp;
        /**
         * 请求host
         */
        String host;
        /**
         * 服务端口
         */
        int port;
        /**
         * 链路id
         */
        String traceId;
        /**
         * skywalking 的traceId
         */
        String skyId;
        /**
         * apm 的 traceId
         */
        String apmId;
        /**
         * 服务地址
         */
        String serverAddr;
        /**
         * http响应码
         */
        int status;
        /**
         * 响应延迟
         */
        long requestTime;
        /**
         * url
         */
        String url;
        /**
         * 请求类型
         */
        String method;
        /**
         * 请求参数
         */
        String query;
        /**
         * 请求方式
         */
        String requestMethod;
        /**
         * 请求协议
         */
        String protocol;
        /**
         * 调用方ip
         */
        String clientIp;
        /**
         * 真实源ip
         */
        String realClientIp;
        /**
         * 调用方
         */
        String rpcFrom;
        /**
         * 调用方接口
         */
        String fromApi;
        /**
         * 调用方分组
         */
        String fromGroup;
        /**
         * 调用方应用版本
         */
        String fromVersion;
        /**
         * 错误信息
         */
        String resultMsg;
        /**
         * 业务码
         */
        String resultCode;
        /** 业务码-短码 */
        int shortCode;
        /** 是否压测请求 0否 1是 */
        int pressure;
        /** 用户id */
        String userId;
        /** 请求头 */
        String headers;
        /** 来源页面 */
        String referer;
        /** 客户端信息 */
        String userAgent;
        /** 入口请求 */
        String originReq;

        public long getTimestamp() {
            return timestamp;
        }

        public void setTimestamp(long timestamp) {
            this.timestamp = timestamp;
        }

        public String getOriginReq() {
            return originReq;
        }

        public void setOriginReq(String originReq) {
            this.originReq = originReq;
        }

        public String getResultMsg() {
            return resultMsg;
        }

        public String getReferer() {
            return referer;
        }

        public void setReferer(String referer) {
            this.referer = referer;
        }

        public String getUserAgent() {
            return userAgent;
        }

        public void setUserAgent(String userAgent) {
            this.userAgent = userAgent;
        }

        public int getPressure() {
            return pressure;
        }

        public void setPressure(int pressure) {
            this.pressure = pressure;
        }

        public void setResultMsg(String resultMsg) {
            this.resultMsg = resultMsg;
        }

        public String getResultCode() {
            return resultCode;
        }

        public String getUserId() {
            return userId;
        }

        public void setUserId(String userId) {
            this.userId = userId;
        }

        public String getHeaders() {
            return headers;
        }

        public void setHeaders(String headers) {
            this.headers = headers;
        }

        public void setResultCode(String resultCode) {
            this.resultCode = resultCode;
        }

        public int getShortCode() {
            return shortCode;
        }

        public void setShortCode(int shortCode) {
            this.shortCode = shortCode;
        }

        public String getVersion() {
            return version;
        }

        public void setVersion(String version) {
            this.version = version;
        }

        public String getApmId() {
            return apmId;
        }

        public void setApmId(String apmId) {
            this.apmId = apmId;
        }

        public String getGroup() {
            return group;
        }

        public void setGroup(String group) {
            this.group = group;
        }

        public String getFromGroup() {
            return fromGroup;
        }

        public void setFromGroup(String fromGroup) {
            this.fromGroup = fromGroup;
        }

        public String getFromVersion() {
            return fromVersion;
        }

        public void setFromVersion(String fromVersion) {
            this.fromVersion = fromVersion;
        }

        public String getAppId() {
            return appId;
        }

        public String getFromApi() {
            return fromApi;
        }

        public void setFromApi(String fromApi) {
            this.fromApi = fromApi;
        }

        public void setAppId(String appId) {
            this.appId = appId;
        }

        public String getHost() {
            return host;
        }

        public void setHost(String host) {
            this.host = host;
        }

        public int getPort() {
            return port;
        }

        public String getTraceId() {
            return traceId;
        }

        public void setTraceId(String traceId) {
            this.traceId = traceId;
        }

        public String getServerAddr() {
            return serverAddr;
        }

        public void setServerAddr(String serverAddr) {
            this.serverAddr = serverAddr;
        }

        public void setPort(int port) {
            this.port = port;
        }

        public String getTime() {
            return time;
        }

        public void setTime(String time) {
            this.time = time;
        }

        public int getStatus() {
            return status;
        }

        public void setStatus(int status) {
            this.status = status;
        }

        public long getRequestTime() {
            return requestTime;
        }

        public void setRequestTime(long requestTime) {
            this.requestTime = requestTime;
        }

        public String getUrl() {
            return url;
        }

        public void setUrl(String url) {
            this.url = url;
        }

        public String getMethod() {
            return method;
        }

        public void setMethod(String method) {
            this.method = method;
        }

        public String getQuery() {
            return query;
        }

        public void setQuery(String query) {
            this.query = query;
        }

        public String getRequestMethod() {
            return requestMethod;
        }

        public void setRequestMethod(String requestMethod) {
            this.requestMethod = requestMethod;
        }

        public String getProtocol() {
            return protocol;
        }

        public void setProtocol(String protocol) {
            this.protocol = protocol;
        }

        public String getClientIp() {
            return clientIp;
        }

        public void setClientIp(String clientIp) {
            this.clientIp = clientIp;
        }

        public String getRealClientIp() {
            return realClientIp;
        }

        public void setRealClientIp(String realClientIp) {
            this.realClientIp = realClientIp;
        }

        public String getRpcFrom() {
            return rpcFrom;
        }

        public String getSkyId() {
            return skyId;
        }

        public void setSkyId(String skyId) {
            this.skyId = skyId;
        }

        public void setRpcFrom(String rpcFrom) {
            this.rpcFrom = rpcFrom;
        }

        @Override
        public String toString() {
            return JSON.toJSONString(this, SerializerFeature.IgnoreNonFieldGetter);
        }
    }


}
